﻿using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Tools.WindowsInstallerXml.Serialize;
using File = System.IO.File;

namespace Microsoft.Tools.WindowsInstallerXml.Extensions
{
    public class VsWebProjectHarvester
        : HarvesterExtension
    {
        #region Statics

        private static readonly XName ProjectTypeGuidsName = XName.Get("ProjectTypeGuids", MsBuildNamespace);
        private static readonly XName ReferenceName = XName.Get("Reference", MsBuildNamespace);
        private static readonly XName HintPathName = XName.Get("HintPath", MsBuildNamespace);
        private static readonly XName PrivateName = XName.Get("Private", MsBuildNamespace);

        private static Boolean IsWebProject(string filePath)
        {
            var doc = XDocument.Load(filePath);

            var ele = (from e in doc.Descendants()
                       where e.Name == ProjectTypeGuidsName
                       select e).FirstOrDefault();

            if (ele != null)
            {
                return ele.Value.IndexOf(WebProjectTypeGuidValue, StringComparison.InvariantCultureIgnoreCase) > -1;
            }

            return false;
        }

        #endregion

        #region Fields

        private const String WebProjectTypeGuidValue = "{349c5851-65df-11da-9384-00065b846f21}";

        private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003";

        #endregion

        #region Constructors

        public VsWebProjectHarvester(VSProjectHarvester harvester)
        {
            ProjectHarvester = harvester;
        }

        #endregion

        #region Overrides

        public override Fragment[] Harvest(String argument)
        {
            ProjectHarvester.Core = Core;

            var fragments = new List<Fragment>(ProjectHarvester.Harvest(argument));

            if (IsWebProject(argument) == false)
            {
                return fragments.ToArray();
            }

            foreach (dynamic fragmentStub in CreateReferencedAssemblyFragment(argument))
            {
                fragments.Insert(0, fragmentStub.Fragment);

                var componentsGroupId = ProjectHarvester.ProjectName + ".Binaries";

                fragments.ForEach(fragment =>
                                  {
                                      ComponentGroup componentGroup =
                                          fragment.Children.OfType<ComponentGroup>().FirstOrDefault(
                                              cg => cg.Id.Equals(componentsGroupId, StringComparison.InvariantCulture));

                                      if (componentGroup != null)
                                      {
                                          var componentRef = new ComponentRef
                                                             {
                                                                 Id = fragmentStub.Component.Id
                                                             };

                                          componentGroup.AddChild(componentRef);
                                      }
                                  });
            }

            fragments.ForEach(fragment =>
                              {
                                  ComponentGroup binariesGroup =
                                      fragment.Children.OfType<ComponentGroup>().FirstOrDefault(
                                          child =>
                                          child.Id != null &&
                                          (child.Id.EndsWith(".Binaries", StringComparison.InvariantCulture) ||
                                           child.Id.EndsWith(".Satellites", StringComparison.InvariantCulture)));

                                  if (binariesGroup != null)
                                  {
                                      IEnumerable<ComponentRef> componentRefs =
                                          binariesGroup.Children.OfType<ComponentRef>();

                                      foreach (var directoryRefs in from componentReference in componentRefs
                                                                    from childFragment in fragments
                                                                    select
                                                                        childFragment.Children.OfType<DirectoryRef>().
                                                                        Where(
                                                                            x =>
                                                                            x.Children.OfType<Component>().Where(
                                                                                y => y.Id == componentReference.Id).
                                                                                FirstOrDefault() != null))
                                      {
                                          directoryRefs.ToList().ForEach(x => x.Id += "_BIN");
                                      }
                                  }
                              });

            return fragments.ToArray();
        }

        #endregion

        #region Methods

        private IEnumerable<dynamic> CreateReferencedAssemblyFragment(string projectFilePath)
        {
            if (projectFilePath == null)
            {
                throw new ArgumentNullException("projectFilePath");
            }

            var doc = XDocument.Load(projectFilePath);

            foreach (var refElem in (from e in doc.Descendants() where e.Name == ReferenceName select e))
            {
                var privElem = refElem.Element(PrivateName);

                if (privElem != null && !string.IsNullOrEmpty(privElem.Value) && bool.Parse(privElem.Value))
                {
                    var hintElem = refElem.Element(HintPathName);

                    if (hintElem != null)
                    {
                        var hintPath = Path.Combine(Path.GetDirectoryName(projectFilePath), hintElem.Value);

                        if (File.Exists(hintPath))
                        {
                            var fragment = new Fragment();

                            var directory = new DirectoryRef
                                            {
                                                Id = ProjectHarvester.DirectoryIds
                                            };

                            fragment.AddChild(directory);

                            var component = new Component
                                            {
                                                Id = Core.GenerateIdentifier("cmp", hintPath),
                                            };

                            directory.AddChild(component);

                            var file = new WindowsInstallerXml.Serialize.File
                                       {
                                           Id = Core.GenerateIdentifier("fil", hintPath),
                                           Source = hintPath
                                       };

                            component.AddChild(file);

                            dynamic fragmentStub = new ExpandoObject();

                            fragmentStub.Fragment = fragment;
                            fragmentStub.Component = component;

                            yield return fragmentStub;
                        }
                    }
                }
            }
        }

        #endregion

        #region Properties

        protected VSProjectHarvester ProjectHarvester { get; set; }

        #endregion
    }
}